/*
 * Copyright (C) 2005 - 2013 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008 - 2013 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010 - 2013 ProjectSkyfire <http://www.projectskyfire.org/>
 *
 * Copyright (C) 2011 - 2013 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef DBCSTORE_H
#define DBCSTORE_H

#include "DBCFileLoader.h"
#include "DBCfmt.h"
#include "Logging/Log.h"
#include "Field.h"
#include "DatabaseWorkerPool.h"
#include "Implementation/WorldDatabase.h"
#include "DatabaseEnv.h"

struct SqlDbc {
	const std::string * formatString;
	const std::string * indexName;
	std::string sqlTableName;
	int32 indexPos;
	int32 sqlIndexPos;
	SqlDbc(const std::string * _filename, const std::string * _format,
			const std::string * _idname, const char * fmt) :
			formatString(_format), indexName(_idname), sqlIndexPos(0) {
		// Convert dbc file name to sql table name
		sqlTableName = *_filename;
		for (uint32 i = 0; i < sqlTableName.size(); ++i) {
			if (isalpha(sqlTableName[i]))
				sqlTableName[i] = tolower(sqlTableName[i]);
			else if (sqlTableName[i] == '.')
				sqlTableName[i] = '_';
		}

		// Get sql index position
		DBCFileLoader::GetFormatRecordSize(fmt, &indexPos);
		if (indexPos >= 0) {
			uint32 uindexPos = uint32(indexPos);
			for (uint32 x = 0; x < formatString->size(); ++x) {
				// Count only fields present in sql
				if ((*formatString)[x] == FT_SQL_PRESENT) {
					if (x == uindexPos)
						break;
					++sqlIndexPos;
				}
			}
		}
	}
};

template<class T>
class DBCStorage {
	typedef std::list<char*> StringPoolList;
public:
	explicit DBCStorage(const char *f) :
			fmt(f), nCount(0), fieldCount(0), indexTable(NULL), m_dataTable(
					NULL) {
	}
	~DBCStorage() {
		Clear();
	}

	T const* LookupEntry(uint32 id) const {
		return (id >= nCount) ? NULL : indexTable[id];
	}
	T* LookupEntryNoConst(uint32 id) const {
		return (id >= nCount) ? NULL : indexTable[id];
	}
	void SetEntry(uint32 id, T* entry) {
		if (id >= nCount) {
			printf("ERROR");
			return;
		}
		indexTable[id] = entry;
	}
	uint32 GetNumRows() const {
		return nCount;
	}
	char const* GetFormat() const {
		return fmt;
	}
	uint32 GetFieldCount() const {
		return fieldCount;
	}

	bool Load(char const* fn, SqlDbc * sql) {
		DBCFileLoader dbc;
		// Check if load was sucessful, only then continue
		if (!dbc.Load(fn, fmt))
			return false;

		uint32 sqlRecordCount = 0;
		uint32 sqlHighestIndex = 0;
		Field *fields = NULL;
		QueryResult result = QueryResult(NULL);
		// Load data from sql
		if (sql) {
			std::string query = "SELECT * FROM " + sql->sqlTableName;
			if (sql->indexPos >= 0)
				query += " ORDER BY " + *sql->indexName + " DESC";
			query += ";";

			result = WorldDatabase.Query(query.c_str());
			if (result) {
				sqlRecordCount = uint32(result->GetRowCount());
				if (sql->indexPos >= 0) {
					fields = result->Fetch();
					sqlHighestIndex = fields[sql->sqlIndexPos].GetUInt32();
				}
				// Check if sql index pos is valid
				if (int32(result->GetFieldCount() - 1) < sql->sqlIndexPos) {
					sLog->outError("Invalid index pos for dbc:'%s'",
							sql->sqlTableName.c_str());
					return false;
				}
			}
		}
		char * sqlDataTable;
		fieldCount = dbc.GetCols();
		m_dataTable = (T*) dbc.AutoProduceData(fmt, nCount,
				(char**&) indexTable, sqlRecordCount, sqlHighestIndex,
				sqlDataTable);

		m_stringPoolList.push_back(
				dbc.AutoProduceStrings(fmt, (char*) m_dataTable));

		// Insert sql data into arrays
		if (result) {
			if (indexTable) {
				uint32 offset = 0;
				uint32 rowIndex = dbc.GetNumRows();
				do {
					if (!fields)
						fields = result->Fetch();

					if (sql->indexPos >= 0) {
						uint32 id = fields[sql->sqlIndexPos].GetUInt32();
						if (indexTable[id]) {
							sLog->outError(
									"Index %d already exists in dbc:'%s'", id,
									sql->sqlTableName.c_str());
							return false;
						}
						indexTable[id] = (T*) &sqlDataTable[offset];
					} else
						indexTable[rowIndex] = (T*) &sqlDataTable[offset];
					uint32 columnNumber = 0;
					uint32 sqlColumnNumber = 0;

					for (; columnNumber < sql->formatString->size();
							++columnNumber) {
						if ((*sql->formatString)[columnNumber]
								== FT_SQL_ABSENT) {
							switch (fmt[columnNumber]) {
							case FT_FLOAT:
								*((float*) (&sqlDataTable[offset])) = 0.0f;
								offset += 4;
								break;
							case FT_IND:
							case FT_INT:
								*((uint32*) (&sqlDataTable[offset])) = uint32(
										0);
								offset += 4;
								break;
							case FT_BYTE:
								*((uint8*) (&sqlDataTable[offset])) = uint8(0);
								offset += 1;
								break;
							case FT_STRING:
								// Beginning of the pool - empty string
								*((char**) (&sqlDataTable[offset])) =
										m_stringPoolList.back();
								offset += sizeof(char*);
								break;
							}
						} else if ((*sql->formatString)[columnNumber]
								== FT_SQL_PRESENT) {
							bool validSqlColumn = true;
							switch (fmt[columnNumber]) {
							case FT_FLOAT:
								*((float*) ((&sqlDataTable[offset]))) =
										fields[sqlColumnNumber].GetFloat();
								offset += 4;
								break;
							case FT_IND:
							case FT_INT:
								*((uint32*) ((&sqlDataTable[offset]))) =
										fields[sqlColumnNumber].GetUInt32();
								offset += 4;
								break;
							case FT_BYTE:
								*((uint8*) ((&sqlDataTable[offset]))) =
										fields[sqlColumnNumber].GetUInt8();
								offset += 1;
								break;
							case FT_STRING:
								sLog->outError(
										"Unsupported data type in table '%s' at char %d",
										sql->sqlTableName.c_str(),
										columnNumber);
								return false;
							case FT_SORT:
								break;
							default:
								validSqlColumn = false;
								break;
							}
							if (validSqlColumn
									&& (columnNumber
											!= (sql->formatString->size() - 1)))
								sqlColumnNumber++;
						} else {
							sLog->outError(
									"Incorrect sql format string '%s' at char %d",
									sql->sqlTableName.c_str(), columnNumber);
							return false;
						}
					}
					if (sqlColumnNumber != (result->GetFieldCount() - 1)) {
						sLog->outError(
								"SQL and DBC format strings are not matching for table: '%s'",
								sql->sqlTableName.c_str());
						return false;
					}

					fields = NULL;
					++rowIndex;
				} while (result->NextRow());
			}
		}

		// error in dbc file at loading if NULL
		return indexTable != NULL;
	}

	bool LoadStringsFrom(char const* fn) {
		// DBC must be already loaded using Load
		if (!indexTable)
			return false;

		DBCFileLoader dbc;
		// Check if load was successful, only then continue
		if (!dbc.Load(fn, fmt))
			return false;

		m_stringPoolList.push_back(
				dbc.AutoProduceStrings(fmt, (char*) m_dataTable));

		return true;
	}

	void Clear() {
		if (!indexTable)
			return;

		delete[] ((char*) indexTable);
		indexTable = NULL;
		delete[] ((char*) m_dataTable);
		m_dataTable = NULL;

		while (!m_stringPoolList.empty()) {
			delete[] m_stringPoolList.front();
			m_stringPoolList.pop_front();
		}
		nCount = 0;
	}

public:
	char const* fmt;
	uint32 nCount;
	uint32 fieldCount;
	T** indexTable;
	T* m_dataTable;
	StringPoolList m_stringPoolList;
};

#endif
